﻿/*
Copyright (c) 2009 Vratislav Cermak (vratacermak@gmail.com)
 
This file is part of TeachMe Project. See http://code.google.com/p/teach-me for more information.
 
TeachMe is an open source project. Using of TeachMe is free of charge.
It is distributed under GNU General Public License version 3 (GPLv3).
GPLv3 can be found at http://www.gnu.org/licenses/gpl.html.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace TeachMe.Application.Win32
{
    internal delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);

    internal sealed class MouseHook : IDisposable
    {
        private SafeMouseHookHandle _hookHandle;
        private LowLevelMouseProc _hookProc;
        private bool _enabled;

        private MouseHook()
        {
            //
        }

        public bool Enabled
        {
            get
            {
                return _enabled;
            }
            set
            {
                _enabled = value;
            }
        }

        public void Dispose()
        {
            if (null != _hookHandle)
            {
                _hookHandle.Dispose();
                _hookHandle = null;
            }

            GC.SuppressFinalize(this);
        }

        public static MouseHook Initialize()
        {
            var classInstance = new MouseHook();

            classInstance._hookProc = new LowLevelMouseProc(classInstance.HookCallback);

            using (Process currentProcess = Process.GetCurrentProcess())
            {
                using (ProcessModule currentModule = currentProcess.MainModule)
                {
                    if (currentModule != null)
                    {
                        classInstance._hookHandle = NativeMethods.SetWindowsHookEx(Constants.WH_MOUSE_LL, classInstance._hookProc, NativeMethods.GetModuleHandle(currentModule.ModuleName), 0);
                    }
                }
            }

            if (classInstance._hookHandle.IsInvalid)
            {
                Logging.Error(typeof(MouseHook), "Hooking to mouse events failed.");
                
                classInstance.Dispose();
            }

            return classInstance;
        }

        ~MouseHook()
        {
            Dispose();
        }

        public event EventHandler<MouseEventArgs> MouseDown;
        public event EventHandler<MouseEventArgs> MouseMove;
        public event EventHandler<MouseEventArgs> MouseUp;
        public event EventHandler<MouseEventArgs> MouseWheel;

        private void OnMouseDown(MouseEventArgs e)
        {
            EventHandler<MouseEventArgs> eventHandler = MouseDown;
            if (null != eventHandler)
            {
                eventHandler(this, e);
            }
        }

        private void OnMouseMove(MouseEventArgs e)
        {
            EventHandler<MouseEventArgs> eventHandler = MouseMove;
            if (null != eventHandler)
            {
                eventHandler(this, e);
            }
        }

        private void OnMouseUp(MouseEventArgs e)
        {
            EventHandler<MouseEventArgs> eventHandler = MouseUp;
            if (null != eventHandler)
            {
                eventHandler(this, e);
            }
        }

        private void OnMouseWheel(MouseEventArgs e)
        {
            EventHandler<MouseEventArgs> eventHandler = MouseWheel;
            if (null != eventHandler)
            {
                eventHandler(this, e);
            }
        }

        private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            MSLLHOOKSTRUCT hookData;

            if (_enabled && nCode <= 0)
            {
                var message = (Messages)wParam;

                switch (message)
                {
                    case Messages.WM_MBUTTONDOWN:
                        // Logging.Debug(GetType(), "WM_MBUTTONDOWN occured.");
                        hookData = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
                        OnMouseDown(new MouseEventArgs(MouseButtons.Middle, 1, hookData.Point.X, hookData.Point.Y, 0));
                        break;

                    case Messages.WM_LBUTTONDOWN:
                        // Logging.Debug(GetType(), "WM_LBUTTONDOWN occured.");
                        hookData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof (MSLLHOOKSTRUCT));
                        OnMouseDown(new MouseEventArgs(MouseButtons.Left, 1, hookData.Point.X, hookData.Point.Y, 0));
                        break;

                    case Messages.WM_RBUTTONDOWN:
                        // Logging.Debug(GetType(), "WM_RBUTTONDOWN occured.");
                        hookData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof (MSLLHOOKSTRUCT));
                        OnMouseDown(new MouseEventArgs(MouseButtons.Right, 1, hookData.Point.X, hookData.Point.Y, 0));
                        break;

                    case Messages.WM_MOUSEMOVE:
                        // Logging.Debug(GetType(), "WM_MOUSEMOVE occured.");
                        hookData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof (MSLLHOOKSTRUCT));
                        OnMouseMove(new MouseEventArgs(MouseButtons.None, 0, hookData.Point.X, hookData.Point.Y, 0));
                        break;

                    case Messages.WM_MBUTTONUP:
                        // Logging.Debug(GetType(), "WM_MBUTTONUP occured.");
                        hookData = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
                        OnMouseUp(new MouseEventArgs(MouseButtons.Middle, 1, hookData.Point.X, hookData.Point.Y, 0));
                        break;

                    case Messages.WM_LBUTTONUP:
                        // Logging.Debug(GetType(), "WM_LBUTTONUP occured.");
                        hookData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof (MSLLHOOKSTRUCT));
                        OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, hookData.Point.X, hookData.Point.Y, 0));
                        break;

                    case Messages.WM_RBUTTONUP:
                        // Logging.Debug(GetType(), "WM_RBUTTONUP occured.");
                        hookData = (MSLLHOOKSTRUCT) Marshal.PtrToStructure(lParam, typeof (MSLLHOOKSTRUCT));
                        OnMouseUp(new MouseEventArgs(MouseButtons.Right, 1, hookData.Point.X, hookData.Point.Y, 0));
                        break;
                    
                    case Messages.WM_MOUSEWHEEL:
                        // Logging.Debug(GetType(), "WM_MOUSEWHEEL occured.");
                        hookData = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
                        OnMouseWheel(new MouseEventArgs(MouseButtons.None, 1, hookData.Point.X, hookData.Point.Y, 0));
                        break;
                }
            }
            
            return NativeMethods.CallNextHookEx(_hookHandle, nCode, wParam, lParam);
        }
    }
}